﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public delegate void DisposeEventHandler (object sender, EventArgs e);

    public abstract class Object3D : IDisposable {

        public event DisposeEventHandler DisposeEvent;

        #region Field
        int                  userID;
        Object               userObject;
        bool                 animationEnabled;
        List<AnimationTrack> animationTracks;
        List<int>            animationChannels;
        List<int>            animationUniforms;
        #endregion


        #region Method
        public Object3D () {
            this.userID = 0;
            this.userObject = null;
            this.animationEnabled = true;
            this.animationTracks = new List<AnimationTrack> ();
            this.animationChannels = new List<int> ();
            this.animationUniforms = new List<int> ();
        }

        protected Object3D (Object3D obj) {
            this.userID = obj.userID;
            this.userObject = obj.userObject;
            this.animationEnabled = obj.animationEnabled;
            this.animationTracks = new List<AnimationTrack> (obj.animationTracks);
            this.animationChannels = new List<int> (obj.animationChannels);
            this.animationUniforms = new List<int> (obj.animationUniforms);
        }

        public void Dispose () {
            if (DisposeEvent != null) {
                DisposeEvent (this, EventArgs.Empty);
            }
        }


        public bool AnimationEnabled {
            get { return animationEnabled; }
            set { animationEnabled = value; }
        }



        public int UserID {
            get { return userID; }
            set { userID = value; }
        }

        public Object UserObject {
            get { return userObject; }
            set { userObject = value; }
        }

        public AnimationTrack[] AnimationTrack {
            get { return animationTracks.ToArray (); }
        }

        public int[] AnimationTrackChannel {
            get { return animationChannels.ToArray (); }
        }

        public int AnimationTrackCount {
            get { return animationTracks.Count (); }
        }

        public int[] AnimationTrackUniform {
            get {
                return animationUniforms.ToArray ();
            }
        }


        public void AddAnimationTrack (AnimationTrack animationTrack) {
            if (animationTrack == null) {
                throw new ArgumentNullException ("AnimationTrack is null");
            }
            // エラー条件後3つ

            this.animationTracks.Add (animationTrack);
            this.animationChannels.Add (0);
            this.animationUniforms.Add (0);
        }

        public void AddAnimationTrack (AnimationTrack animationTrack, int channel) {
            if (animationTrack == null) {
                throw new ArgumentNullException ("AnimationTrack is null");
            }
            // エラー条件2つ
            int channelCount = animationTrack.KeyframeSequence.ChanelCount;
            if (channel < 0 || channel >= channelCount) {
                throw new ArgumentOutOfRangeException ("Channel index must be within [0," + (channelCount - 1) + "]");
            }

            this.animationTracks.Add (animationTrack);
            this.animationChannels.Add (channel);
            this.animationUniforms.Add (0);
        }

        protected void AddAnimationTrackUniform (AnimationTrack animationTrack, int channel, int uniform) {

            this.animationTracks.Add (animationTrack);
            this.animationChannels.Add (channel);
            this.animationUniforms.Add (uniform);
        }


        public int FindAnimationEvents (int startTime, int endTime, uint eventMask, AnimationEvent[] events) {
            var query = FindAnimationEvents (startTime, endTime, eventMask);
            Array.Copy (query, events, Math.Min (query.Length, events.Length));
            return query.Length;
        }



        public AnimationEvent[] FindAnimationEvents (int startTime, int endTime, uint eventMask) {
            if (startTime > endTime) {
                throw new ArgumentException ("StartTime must be less than endTime, start=" + startTime + ",end=" + endTime);
            }

            var query = new List<AnimationEvent> ();
            foreach (var track in AnimationTrack) {
                var kseq = track.KeyframeSequence;
                var ctrl = track.Controller;
                if (ctrl == null) {
                    continue;
                }

                for (var i=0; i < kseq.EventCount; i++) {
                    var time = ctrl.WorldTime(kseq.EventTime[i]);
                    var id   = kseq.EventID[i];
                    if (kseq.RepeatMode == KeyframeSequence.Loop || kseq.RepeatMode == KeyframeSequence.AdditiveLoop) {
                        ShiftTime (ref time, startTime, endTime, (int)(kseq.Duration / ctrl.Speed));
                    }
                    if (ctrl.Active (time) == false) {
                        continue;
                    }
                    if (time >= startTime && time <= endTime && (id & eventMask) != 0) {
                        query.Add (new AnimationEvent (time, id, track, this));
                    }
                }
            }


            foreach (var obj in References) {
                query.AddRange (obj.FindAnimationEvents (startTime, endTime, eventMask));
            }

            query.Sort ();
            return query.ToArray();

        }

        void ShiftTime (ref int time, int startTime, int endTime, int duration) {
            while (time < startTime || time > endTime) {
                if (time < startTime) {
                    time += duration;
                    if (time > endTime) {
                        break;
                    }
                }
                if (time > endTime) {
                    time -= duration;
                    if (time < startTime) {
                        break;
                    }
                }
            }
        }

        public void RemoveAnimationTrack (AnimationTrack animationTrack) {

            while (animationTracks.Contains (animationTrack)) {
                var index = animationTracks.IndexOf (animationTrack);
                this.animationTracks.RemoveAt (index);
                this.animationChannels.RemoveAt (index);
                this.animationUniforms.RemoveAt (index);
            }
        }


        public virtual Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (animationTracks);
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public int GetReferences (Object3D[] references) {
            var query = References;
            if (references != null) {
                if (references.Length < query.Length) {
                    throw new ArgumentException ("Length of references is less than required, len=" + references.Length + ",req=" + query.Length);
                }
                Array.Copy (query, references, query.Length);
            }

            return query.Length;
        }

        public Object3D Find (int userID) {
            if (userID == this.userID) {
                return this;
            }
            foreach (var obj in References) {
                var q = obj.Find (userID);
                if (q != null) {
                    return q;
                }
            }
            return null;
        }


        public T[] FindAll<T> () where T : Object3D {
            var q = new List<T> ();
            if (this is T) {
                q.Add (this as T);
            }
            foreach (var obj in References) {
                q.AddRange (obj.FindAll<T> ());
            }
            return new HashSet<T> (q).ToArray ();
        }

        //public virtual void Refresh () {
        //    foreach (var obj in References) {
        //        obj.Refresh ();
        //    }
        //}

        public virtual Object3D Duplicate () {

            return null;
        }


        public virtual bool ReplaceMatching (Object3D[] objects) {
            if (objects == null) {
                throw new ArgumentNullException ("Objects is null");
            }

            // 未実装
            return false;
        }

        public virtual int Animate (int time) {
            foreach (var obj in References) {
                obj.Animate (time);
            }
            return 0;
        }



        #endregion

    }
}
